package jinyilw.common.file;

import jinyilw.common.math.MathTools;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;

/**
 * 传输工具
 */
public class TransferTools
{
	// 超时
	public static final int SO_TIMEOUT = 10000;

	private static final int EOF = -1;
	private static final int KSIZE = 1024;
	private static final int BUFFER_SIZE = 10240;
	// private static final int FLUSH_SIZE = BUFFER_SIZE * 100;

	/**
	 * 测速间隔
	 */
	private static final int SPEED_INTERVAL = 100;

	/**
	 * 接收写入文件
	 */
	public static File receiveFile(DataInputStream dataInputStream, File tofile,
			long fileLen, long startLen, Transfer transfer) throws IOException
	{
		if (transfer == null)
			return receiveFile(dataInputStream, tofile, startLen);
		if (dataInputStream == null || tofile == null)
			return null;
		long beginTime = System.currentTimeMillis();
		long transLen = 0;
		int n;
		FileOutputStream fileOutputStream;

		if (startLen > 0)
		{
			fileOutputStream = new FileOutputStream(tofile, true);
			transLen = startLen;
		} else
		{
			fileOutputStream = new FileOutputStream(tofile);
		}

		// 计时器
		long timea = System.currentTimeMillis();
		long sizea = transLen;
		String fileSize = takeFileSize(fileLen, 3);
		float percent = takePercent(transLen, fileLen);
		// 显示信息
		transfer.initStatus(tofile.getName(), fileSize, percent);

		byte[] buffer = new byte[BUFFER_SIZE];
		while (EOF != (n = dataInputStream.read(buffer)))
		{
			fileOutputStream.write(buffer, 0, n);
			transLen += n;

			//			if (count % FLUSH_SIZE == 0) {
			//				System.out.println("传输进度：" + (100 * count / fileLen) + "%");
			//			}
			if (System.currentTimeMillis() - timea > SPEED_INTERVAL)
			{
				transfer.processStatus(takePercent(transLen, fileLen),
						takeSpeed(System.currentTimeMillis() - timea,
								transLen - sizea),
						takeUsedTime(beginTime, System.currentTimeMillis()));
				timea = System.currentTimeMillis();
				sizea = transLen;
			}
		}
		dataInputStream.close();
		fileOutputStream.close();
		transfer.endStatus(tofile, fileLen, transLen);
		return tofile;
	}

	public static File receiveFile(DataInputStream dataInputStream, File toFile,
			long startLen) throws IOException
	{
		if (dataInputStream == null || toFile == null)
			return null;
		int n;
		FileOutputStream fileOutputStream;
		if (startLen > 0)
		{
			fileOutputStream = new FileOutputStream(toFile, true);
		} else
		{
			fileOutputStream = new FileOutputStream(toFile);
		}
		byte[] buffer = new byte[BUFFER_SIZE];
		while (EOF != (n = dataInputStream.read(buffer)))
			fileOutputStream.write(buffer, 0, n);
		dataInputStream.close();
		fileOutputStream.close();
		return toFile;
	}

	static File[] receiveFiles(DataInputStream dataInputStream, File[] toFiles,
			long[] fileLens) throws IOException
	{
		if (dataInputStream == null || toFiles == null)
			return null;
		int n;
		long remainLen;
		FileOutputStream fileOutputStream;
		byte[] buffer = new byte[BUFFER_SIZE];
		for (int i = 0; i < toFiles.length; i++)
		{
			fileOutputStream = new FileOutputStream(toFiles[i]);
			remainLen = fileLens[i];
			while (true)
			{
				if (remainLen < BUFFER_SIZE)
				{
					if (remainLen > 0)
					{
						byte[] reBuffer = new byte[(int) remainLen];
						dataInputStream.readFully(reBuffer);
						fileOutputStream.write(reBuffer);
					}
					break;
				} else
				{
					n = dataInputStream.read(buffer);
					fileOutputStream.write(buffer, 0, n);
					remainLen -= n;
				}
			}
			fileOutputStream.flush();
			fileOutputStream.close();
		}
		// dataInputStream.close();
		return toFiles;
	}

	public static void sendFiles(DataOutputStream dataOutputStream,
			File... files) throws IOException
	{
		if (dataOutputStream == null || files == null)
			return;
		dataOutputStream.writeShort(files.length);
		for (File file : files)
		{
			dataOutputStream.writeUTF(file.getName());
			dataOutputStream.writeLong(file.length());
		}
		// 刷新缓冲
		dataOutputStream.flush();
		int n;
		byte[] buffer = new byte[BUFFER_SIZE];
		for (File file : files)
		{
			FileInputStream fileInputStream = new FileInputStream(file);
			while ((n = fileInputStream.read(buffer)) != EOF)
			{
				dataOutputStream.write(buffer, 0, n);
			}
			// 刷新缓冲
			dataOutputStream.flush();
			fileInputStream.close();
		}
	}

	/**
	 * 发送文件
	 */
	public static void sendFile(DataOutputStream dataOutputStream, File file,
			long startLen, Transfer transfer) throws IOException
	{
		if (transfer == null)
		{
			sendFile(dataOutputStream, file, startLen);
			return;
		}
		long fileLen = file.length();
		long beginTime = System.currentTimeMillis();

		FileInputStream fileInputStream = new FileInputStream(file);
		long transLen = 0;
		int n;
		if (startLen > 0)
		{
			fileInputStream.skip(startLen);
			transLen = startLen;
		}

		// 计时器
		long timea = System.currentTimeMillis();
		long sizea = transLen;
		String fileSize = takeFileSize(fileLen, 3);
		float percent = takePercent(transLen, fileLen);
		// 显示信息
		transfer.initStatus(file.getName(), fileSize, percent);

		final byte[] buffer = new byte[BUFFER_SIZE];

		while (EOF != (n = fileInputStream.read(buffer)))
		{
			dataOutputStream.write(buffer, 0, n);
			transLen += n;

			//			// 数据刷新间隔
			//			if (count % FLUSH_SIZE == 0) {
			////                dataOutputStream.flush();
			//			}
			// 测速
			if (System.currentTimeMillis() - timea > SPEED_INTERVAL)
			{
				// 显示信息
				transfer.processStatus(takePercent(transLen, fileLen),
						takeSpeed(System.currentTimeMillis() - timea,
								transLen - sizea),
						takeUsedTime(beginTime, System.currentTimeMillis()));
				timea = System.currentTimeMillis();
				sizea = transLen;
			}
		}
		// 刷新缓冲
		dataOutputStream.flush();
		fileInputStream.close();
		transfer.endStatus(file, fileLen, transLen);
	}

	public static void sendFile(DataOutputStream dataOutputStream, File file,
			long startLen) throws IOException
	{
		FileInputStream fileInputStream = new FileInputStream(file);
		int n;
		if (startLen > 0)
		{
			fileInputStream.skip(startLen);
		}

		byte[] buffer = new byte[BUFFER_SIZE];
		while (EOF != (n = fileInputStream.read(buffer)))
			dataOutputStream.write(buffer, 0, n);
		// 刷新缓冲
		dataOutputStream.flush();
		fileInputStream.close();
	}

	public static void sendFileToNetTemporarily(String ip, int port, File file,
			Transfer transfer)
	{
		if (ip == null || file == null || !file.isFile())
			return;
		transferFileTemporarily(ip, port, file, transfer, true);
	}

	public static File receiveFileFromNetTemporarily(String ip, int port,
			File directory, Transfer transfer)
	{
		if (ip == null)
			return null;
		return transferFileTemporarily(ip, port, directory, transfer, false);
	}

	public static void sendFilesToNetTemporarily(String ip, int port,
			File[] files)
	{
		if (ip == null || files == null)
			return;
		Socket socket = createSocket(ip, port);
		if (socket == null)
			return;
		sendFilesToSocket(socket, files);
		try
		{
			socket.close();
		} catch (IOException e)
		{
			e.printStackTrace();
		}
	}

	public static File[] receiveFilesFromNetTemporarily(String ip, int port,
			File directory)
	{
		if (ip == null)
			return null;
		Socket socket = createSocket(ip, port);
		if (socket == null)
			return null;
		File[] files = receiveFilesFromSocket(socket, directory);
		try
		{
			socket.close();
		} catch (IOException e)
		{
			e.printStackTrace();
		}
		return files;
	}

	private static File transferFileTemporarily(String ip, int port, File file,
			Transfer transfer, boolean isSend)
	{
		Socket socket = createSocket(ip, port);
		if (socket == null)
			return null;
		if (isSend)
			sendFileToSocket(socket, file, 0, transfer);
		else
			file = receiveFileFromSocket(socket, file, 0, transfer);
		try
		{
			socket.close();
		} catch (IOException e)
		{
			e.printStackTrace();
		}
		return file;
	}

	private static Socket createSocket(String ip, int port)
	{
		try (Socket socket = new Socket(ip, port))
		{
			return socket;
		} catch (Exception e)
		{
			return null;
		}
	}

	public static void sendFileToNetTemporarily(int port, File file,
			Transfer transfer)
	{
		if (file == null || !file.isFile())
			return;
		transferFileTemporarily(port, file, transfer, true);
	}

	public static File receiveFileFromNetTemporarily(int port, File directory,
			Transfer transfer)
	{
		return transferFileTemporarily(port, directory, transfer, false);
	}

	public static void sendFilesToNetTemporarily(int port, File[] files)
	{
		if (files == null || files.length == 0)
			return;
		int num = 0;
		boolean[] isFile = new boolean[files.length];
		for (int i = 0; i < files.length; i++)
		{
			isFile[i] = files[i] != null && files[i].isFile();
			if (isFile[i])
				num++;
		}
		if (num == 0)
			return;
		File[] existFiles = new File[num];
		for (int i = 0, j = 0; i < isFile.length; i++)
			if (isFile[i])
			{
				existFiles[j] = files[i];
				j++;
			}

		Object[] objects = acceptSocket(port);
		if (objects == null)
			return;
		ServerSocket serverSocket = (ServerSocket) objects[0];
		Socket socket = (Socket) objects[1];
		sendFilesToSocket(socket, existFiles);
		try
		{
			serverSocket.close();
			socket.close();
		} catch (IOException e)
		{
			e.printStackTrace();
		}
	}

	public static File[] receiveFilesFromNetTemporarily(int port,
			File directory)
	{
		Object[] objects = acceptSocket(port);
		if (objects == null)
			return null;
		ServerSocket serverSocket = (ServerSocket) objects[0];
		Socket socket = (Socket) objects[1];
		File[] files = receiveFilesFromSocket(socket, directory);
		try
		{
			serverSocket.close();
			socket.close();
		} catch (IOException e)
		{
			e.printStackTrace();
		}
		return files;
	}

	private static File transferFileTemporarily(int port, File file,
			Transfer transfer, boolean isSend)
	{
		Object[] objects = acceptSocket(port);
		if (objects == null)
			return null;
		ServerSocket serverSocket = (ServerSocket) objects[0];
		Socket socket = (Socket) objects[1];
		if (isSend)
			sendFileToSocket(socket, file, 0, transfer);
		else
			file = receiveFileFromSocket(socket, file, 0, transfer);
		try
		{
			serverSocket.close();
			socket.close();
		} catch (IOException e)
		{
			e.printStackTrace();
		}
		return file;
	}

	private static Object[] acceptSocket(int port)
	{
		try (ServerSocket serverSocket = new ServerSocket(port))
		{
			serverSocket.setSoTimeout(SO_TIMEOUT);
			return new Object[]{serverSocket, serverSocket.accept()};
		} catch (IOException e)
		{
			e.printStackTrace();
			return null;
		}
	}

	public static File receiveFileFromSocket(Socket socket, File directory)
	{
		return receiveFileFromSocket(socket, directory, 0, null);
	}

	public static void sendFileToSocket(Socket socket, File file)
	{
		sendFileToSocket(socket, file, 0, null);
	}

	public static File receiveFileFromSocket(Socket socket, File directory,
			long startLen, Transfer transfer)
	{
		if (socket == null)
			return null;
		try
		{
			DataInputStream dataInputStream = new DataInputStream(
					socket.getInputStream());
			File toFile = new File(directory, dataInputStream.readUTF());
			long fileLen = dataInputStream.readLong();
			return receiveFile(dataInputStream, toFile, fileLen, startLen,
					transfer);
		} catch (IOException e)
		{
			e.printStackTrace();
			return null;
		}
	}

	public static void sendFileToSocket(Socket socket, File file, long startLen,
			Transfer transfer)
	{
		if (socket == null || file == null || !file.isFile())
			return;
		try
		{
			DataOutputStream dataOutputStream = new DataOutputStream(
					socket.getOutputStream());
			dataOutputStream.writeUTF(file.getName());
			dataOutputStream.writeLong(file.length());
			sendFile(dataOutputStream, file, startLen, transfer);
		} catch (IOException e)
		{
			e.printStackTrace();
		}
	}

	public static void sendFilesToSocket(Socket socket, File[] files)
	{
		if (socket == null || files == null)
			return;
		try
		{
			DataOutputStream dataOutputStream = new DataOutputStream(
					socket.getOutputStream());
			sendFiles(dataOutputStream, files);
			dataOutputStream.close();
		} catch (IOException e)
		{
			e.printStackTrace();
		}
	}

	public static File[] receiveFilesFromSocket(Socket socket, File directory)
	{
		if (socket == null)
			return null;
		try
		{
			DataInputStream dataInputStream = new DataInputStream(
					socket.getInputStream());
			int num = dataInputStream.readShort();
			File[] toFiles = new File[num];
			long[] fileLens = new long[num];
			for (int i = 0; i < num; i++)
			{
				toFiles[i] = new File(directory, dataInputStream.readUTF());
				fileLens[i] = dataInputStream.readLong();
			}
			File[] files = receiveFiles(dataInputStream, toFiles, fileLens);
			dataInputStream.close();
			return files;
		} catch (IOException e)
		{
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * 获取文件夹大小
	 */
	public static long takeFileOrDirSize(String path)
	{
		File file = new File(path);
		if (file.exists())
		{
			if (file.isFile())
			{
				return file.length();
			} else if (file.isDirectory())
			{
				System.out.println(takeFileSize(file.length(), 3));
				System.out.println(takeFileSize(file.getTotalSpace(), 3));
				System.out.println(takeFileSize(file.getUsableSpace(), 3));
				System.out.println(takeFileSize(file.getFreeSpace(), 3));
				return file.getUsableSpace();
			}
		}
		return 0;
	}

	/**
	 * 显示文件大小
	 */
	public static String takeFileSize(long fileSize, int pointLen)
	{
		double size = fileSize;
		if (size < KSIZE)
			return size + " Byte";

		size /= KSIZE;
		if (size < KSIZE)
			return MathTools.roundString(size, pointLen) + " KB";

		size /= KSIZE;
		if (size < KSIZE)
			return MathTools.roundString(size, pointLen) + " MB";

		size /= KSIZE;
		if (size < KSIZE)
			return MathTools.roundString(size, pointLen) + " GB";

		size /= KSIZE;
		if (size < KSIZE)
			return MathTools.roundString(size, pointLen) + " TB";

		size /= KSIZE;
		if (size < KSIZE)
			return MathTools.roundString(size, pointLen) + " PB";

		// 最终单位，long的最大值为8EiB
		size /= KSIZE;
		return MathTools.roundString(size, pointLen) + " EB";
	}

	/**
	 * 显示速度
	 */
	public static String takeSpeed(long time, long size)
	{
		double second = time / 1000.0;
		long speed = 0;
		if (second > 0)
		{
			speed = (long) (size / second);
		}
		return takeFileSize(speed, 2) + "/s";

	}

	/**
	 * 显示已用时长
	 */
	public static String takeUsedTime(long start, long now)
	{
		long time = now - start;
		long hours = time / 3600000;
		time = time % 3600000;
		long minutes = time / 60000;
		time = time % 60000;
		long second = time / 1000;
		String show = second + "s";
		if (minutes > 0)
			show = minutes + "m " + show;
		if (hours > 0)
			show = hours + "h " + show;
		return show;
	}

	/**
	 * 显示传输进度
	 */
	public static float takePercent(long current, long total)
	{
		// 修正分母为0的情况
		if (total == 0L)
			return 0;
		return 100 * current / (float) total;
	}

	/**
	 * 获取该文件夹下所有文件，包括子文件夹内的
	 */
	public static long takeAllFileSize(File dir)
	{
		List<File> list = FileTools.getAllFiles(dir);
		long filesize = 0;
		for (File file : list)
		{
			filesize += file.length();
		}
		return filesize;
	}

	/**
	 * 获取从基础路径开始到文件之间的路径
	 */
	public static String getMiddleParentPath(File basefile, File file)
	{
		if (null == basefile || null == file)
		{
			return null;
		}
		String rootParentPath = basefile.getParent();
		String fileParentPath = file.getParent();
		return fileParentPath.replace(rootParentPath, "");
	}
}
